package com.obrock;

import android.database.Cursor;

import java.io.Serializable;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: Rick
 * Date: 21.09.12
 * Time: 22:59
 * To change this template use File | Settings | File Templates.
 */
public class Balance implements Serializable, Comparable {

    // тип проводки
    public static final int TYPE_NORMAL = 0;
    public static final int TYPE_COMPLEX = 1;

    // тип записи
    public static final int MODE_NORMAL = 0;
    public static final int MODE_PROGNOSIS = 1;
    public static final int MODE_TEMPLATE = 2;

    // младшие 8 бита - тип периодичности
    public static final int FLAG_PERIOD_ONCE = 0x0001;
    public static final int FLAG_PERIOD_DAILY = 0x0002;
    public static final int FLAG_PERIOD_WEEKLY = 0x0003;
    public static final int FLAG_PERIOD_MONTHLY = 0x0004;
    public static final int FLAG_PERIOD_ANNUAL = 0x0005;
    public static final int FLAG_PERIOD_CREDIT = 0x0006;

    // для шаблона - немедленная проводка
    public static final int FLAG_FORCE_TRANSFER = 0x0100;
    // проводка создана из запланированной
    public static final int FLAG_INHERIT_TRANSFER = 0x0200;
    // проводка создана из шаблона
    public static final int FLAG_INHERIT_TEMPLATE = 0x0400;


    public long balanceID = -1;
    public long parentID = -1;
    public String name = "";
    public int type = TYPE_NORMAL;
    public Date date = new Date();
    public Account accountFrom = null;
    public Account accountTo = null;
    public double amount = 0.0;
    public int mode = MODE_NORMAL;
    public int flags = 0;
    public int period = 1;
    public String number = "";
    public String template = "";
    public String key = "";
    public String remark = "";
    public Map<Analytics, Analytics> analytics = new HashMap<Analytics, Analytics>(0);
    public List<Complex> complex = new ArrayList<Complex>(0);

    private static Database db = null;

    public boolean approved = false;
    //public boolean inserted = false;

    public Balance(Database db, long id) {
        this(db);
        balanceID = id;
        refresh();
    }

    public Balance(Database db) {
        this.db = db;
    }

    public void refresh() {
        Cursor cr = db.getReadableDatabase().rawQuery("SELECT "
                // 0   1     2     3     4        5      6       7       8
                + "id, name, type, date, id_from, id_to, amount, remark, id_parent, mode, flags, period, number, template, key "
                + "FROM balance "
                + "WHERE id = ?;",
                new String[]{"" + balanceID});
        if (cr.moveToFirst()) {
            balanceID = cr.getLong(0);
            name = cr.getString(1);
            type = cr.getInt(2);
            date = new Date(cr.getLong(3));
            accountFrom = Main.context != null ? Main.getAccount(cr.getLong(4)) : new Account(db, cr.getLong(4));
            accountTo = Main.context != null ? Main.getAccount(cr.getLong(5)) : new Account(db, cr.getLong(5));
            amount = cr.getDouble(6);
            remark = cr.getString(7);
            parentID = cr.getLong(8);
            mode = cr.getInt(9);
            flags = cr.getInt(10);
            period = cr.getInt(11);
            number = cr.getString(12);
            template = cr.getString(13);
            key = cr.getString(14);
        }
        cr.close();

        refreshComplex();
        refreshAnalytics();
    }

    public void refreshComplex() {
        complex.clear();
        int realType = type;
        Cursor cr = db.getReadableDatabase().rawQuery("SELECT id " +
                "FROM complex " +
                "WHERE id_balance = ? ",
                new String[]{"" + balanceID});
        if (cr.moveToFirst()) {
            realType = TYPE_COMPLEX;
            amount = 0.0;
            do {
                Complex c = new Complex(db, cr.getLong(0));
                amount += c.amount;
                complex.add(c);
            } while (cr.moveToNext());
        } else {
            realType = TYPE_NORMAL;
        }
        cr.close();

        if (realType != type) {
            type = realType;
            db.getWritableDatabase().execSQL("UPDATE balance SET "
                    + "type = ? "
                    + "WHERE id = ?;",
                    new Object[]{type, balanceID});
            DropboxThread.dataChanged();
        }
    }

    public void refreshAnalytics() {
        analytics.clear();
        Cursor cr;
        if (accountFrom != null) {
            for (Analytics an : accountFrom.analytics) {
                if (!linked(an)) {
                    cr = db.getReadableDatabase().rawQuery("SELECT links.id_analytics "
                            + "FROM links, analytics "
                            + "WHERE id_balance = ? "
                            + "AND links.id_analytics = analytics.id "
                            + "AND analytics.id_parent = ?",
                            new String[]{"" + balanceID, "" + an.analyticsID});
                    if (cr.moveToFirst()) {
                        analytics.put(an, new Analytics(db, null, cr.getLong(0)));
                    } else {
                        analytics.put(an, null);
                    }
                }
            }
        }
        if (accountTo != null && type != TYPE_COMPLEX) {
            for (Analytics an : accountTo.analytics) {
                if (!linked(an)) {
                    cr = db.getReadableDatabase().rawQuery("SELECT links.id_analytics "
                            + "FROM links, analytics "
                            + "WHERE id_balance = ? "
                            + "AND links.id_analytics = analytics.id "
                            + "AND analytics.id_parent = ?",
                            new String[]{"" + balanceID, "" + an.analyticsID});
                    if (cr.moveToFirst()) {
                        analytics.put(an, new Analytics(db, null, cr.getLong(0)));
                    } else {
                        analytics.put(an, null);
                    }
                }
            }
        } else {
            for (Complex c : complex) {
                if (c.account == null) continue;
                for (Analytics an : c.account.analytics) {
                    if (!linked(an)) {
                        cr = db.getReadableDatabase().rawQuery("SELECT links.id_analytics "
                                + "FROM links, analytics "
                                + "WHERE id_balance = ? "
                                + "AND links.id_analytics = analytics.id "
                                + "AND analytics.id_parent = ?",
                                new String[]{"" + balanceID, "" + an.analyticsID});
                        if (cr.moveToFirst()) {
                            analytics.put(an, new Analytics(db, null, cr.getLong(0)));
                        } else {
                            analytics.put(an, null);
                        }
                    }
                }
            }
        }

    }

    /*
    public void recalcComplex() {
        if (complex.size() > 0) {
            type = TYPE_COMPLEX;
            amount = 0.0;
            for (Complex c : complex) {
                amount += c.amount;
            }
        } else {
            type = TYPE_NORMAL;
        }

    }
    */

    public void recalcAnalytics() {
        Map<Analytics, Analytics> tmp = new HashMap<Analytics, Analytics>(0);
        //analytics.clear();
        if (accountFrom != null && accountFrom.accountID != -1) {
            for (Analytics an : accountFrom.analytics) {
                if (!tmp.containsKey(an)) {
                    if (linkedKey(an)) {
                        tmp.put(an, getValue(an));
                    } else {
                        tmp.put(an, null);
                    }
                }
            }
        }
        if (accountTo != null && accountTo.accountID != -1 && type != TYPE_COMPLEX) {
            for (Analytics an : accountTo.analytics) {
                if (!tmp.containsKey(an)) {
                    if (linkedKey(an)) {
                        tmp.put(an, getValue(an));
                    } else {
                        tmp.put(an, null);
                    }
                }
            }
        } else {
            for (Complex c : complex) {
                if (c.account == null || c.account.accountID == -1) continue;
                for (Analytics an : c.account.analytics) {
                    if (!tmp.containsKey(an)) {
                        if (linkedKey(an)) {
                            tmp.put(an, getValue(an));
                        } else {
                            tmp.put(an, null);
                        }
                    }
                }
            }
        }

        analytics = tmp;

    }


    public void append() {
        //refreshComplex();
        db.getWritableDatabase().execSQL("INSERT INTO balance "
                + "(id_parent, name, type, date, id_from, id_to, amount, remark, mode, flags, period, number, template, key) "
                + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
                new Object[]{parentID, name, type, date.getTime(),
                        accountFrom == null ? -1 : accountFrom.accountID,
                        accountTo == null ? -1 : accountTo.accountID,
                        amount,
                        remark,
                        mode,
                        flags,
                        period,
                        number,
                        template,
                        key});
        balanceID = db.getIdent("balance");

        for (Complex c : complex) {
            c.parentID = balanceID;
            c.append();
        }
        for (Analytics a : analytics.values()) {
            if (a != null && a.analyticsID != -1) {
                link(a);
            }
        }

        /*
        db.getWritableDatabase().execSQL("UPDATE complex "
                + "SET id_balance = ? "
                + "WHERE id_balance = -1 "
                + "AND type = ?",
                new Object[]{balanceID, Complex.TYPE_NORMAL});

        db.getWritableDatabase().execSQL("UPDATE links "
                + "SET id_balance = ? "
                + "WHERE id_balance = -1 AND type = ?",
                new Object[]{balanceID, Analytics.TYPE_NORMAL});
        */

        DropboxThread.dataChanged();
    }

    public void update() {
        //refreshComplex();
        db.getWritableDatabase().execSQL("UPDATE balance SET "
                + "id_parent=?, name=?, type=?, date=?, id_from=?, id_to=?, amount=?, remark=?, mode=?, flags=?, period=?, number=?, template=?, key = ? "
                + "WHERE id=?;",
                new Object[]{parentID, name, type, date.getTime(),
                        accountFrom == null ? -1 : accountFrom.accountID,
                        accountTo == null ? -1 : accountTo.accountID,
                        amount,
                        remark,
                        mode,
                        flags,
                        period,
                        number,
                        template,
                        key,
                        balanceID});

        db.getWritableDatabase().execSQL("DELETE FROM complex "
                + "WHERE id_balance = ? ",
                new Object[]{balanceID});
        db.getWritableDatabase().execSQL("DELETE FROM links "
                + "WHERE id_balance = ? ",
                new Object[]{balanceID});

        for (Complex c : complex) {
            c.append();
        }
        for (Analytics a : analytics.values()) {
            if (a != null && a.analyticsID != -1) {
                link(a);
            }
        }

        DropboxThread.dataChanged();
    }

    public void delete() {
        db.getWritableDatabase().execSQL("DELETE FROM complex "
                + "WHERE id_balance = ? ",
                new Object[]{balanceID});
        db.getWritableDatabase().execSQL("DELETE FROM links "
                + "WHERE id_balance = ? ",
                new Object[]{balanceID});
        db.getWritableDatabase().execSQL("DELETE FROM balance "
                + "WHERE id = ?",
                new Object[]{balanceID});
        DropboxThread.dataChanged();
    }

    boolean linked(Analytics an) {
        if (an == null) {
            return false;
        }
        for (Analytics a : analytics.values()) {
            if (a != null && a.analyticsID == an.analyticsID) {
                return true;
            }
        }
        return false;
    }

    boolean linkedKey(Analytics an) {
        if (an == null) {
            return false;
        }
        for (Analytics a : analytics.keySet()) {
            if (a.analyticsID == an.analyticsID) {
                return true;
            }
        }
        return false;
    }

    Analytics getValue(Analytics an) {
        if (an == null) {
            return null;
        }
        for (Analytics a : analytics.keySet()) {
            if (a.analyticsID == an.analyticsID) {
                return analytics.get(a);
            }
        }
        return null;
    }

    Analytics getValue(long analyticsID) {
        if (analyticsID == -1) {
            return null;
        }
        for (Analytics a : analytics.keySet()) {
            if (a.analyticsID == analyticsID) {
                return analytics.get(a);
            }
        }
        return null;
    }


    public void linkVirtual(Analytics an) {
        if (an == null) {
            return;
        }
        for (Analytics a : analytics.keySet()) {
            if (a.analyticsID == an.parentID) {
                //a.refresh();
                analytics.put(a, an);
                return;
            }
        }
    }

    public void unlinkVirtual(Analytics parent) {
        if (parent == null) {
            return;
        }
        for (Analytics a : analytics.keySet()) {
            if (a.analyticsID == parent.analyticsID) {
                analytics.put(a, null);
            }
        }
    }

    public void link(Analytics analytics) {
        if (analytics == null) {
            return;
        }
        //if (!linked(analytics)) {
        db.getWritableDatabase().execSQL("DELETE FROM links "
                + "WHERE id_balance = ? "
                + "AND id_analytics IN (SELECT id FROM analytics WHERE id_parent = ?)",
                new Object[]{balanceID, analytics.parentID});
        db.getWritableDatabase().execSQL("INSERT INTO "
                + "links (id_balance, type, id_analytics) "
                + "VALUES (?,?,?)",
                new Object[]{balanceID, mode, analytics.analyticsID});

        DropboxThread.dataChanged();
        linkVirtual(analytics);
        //}
    }

    public void unlink(Analytics parent) {
        if (parent == null) {
            return;
        }
        db.getWritableDatabase().execSQL("DELETE FROM links "
                + "WHERE id_balance = ? "
                + "AND id_analytics IN (SELECT id FROM analytics WHERE id_parent = ?)",
                new Object[]{balanceID, parent.analyticsID});
        DropboxThread.dataChanged();
        unlinkVirtual(parent);
    }

    public Analytics getParent(Analytics a) {
        if (a == null) {
            return null;
        }
        for (Analytics an : analytics.keySet()) {
            if (an.analyticsID == a.parentID)
                return an;
        }
        return null;
    }

    public boolean isApproved(Calendar[] period) {
        boolean result = false;

        Cursor cr;
        if (mode == MODE_PROGNOSIS) {
            if (period != null) {
                Calendar start = period[0];
                Calendar stop = period[1];
                cr = db.getReadableDatabase().rawQuery(
                        "SELECT balance.id  "
                                + "FROM balance "
                                + "  LEFT JOIN complex ON complex.id_balance = balance.id "
                                + "WHERE (balance.id_parent = "+parentID+" OR COALESCE(complex.id_ref,-1) = "+parentID+") "
                                + "AND date >= "+start.getTimeInMillis()+" "
                                + "AND date <= "+stop.getTimeInMillis()+" "
                                + "AND mode = " + MODE_NORMAL, null);

                /*
                        new String[]{"" + parentID,
                                "" + parentID,
                                "" + start.getTimeInMillis(),
                                "" + stop.getTimeInMillis(),
                                "" + MODE_NORMAL});
                                */
            } else {

                cr = db.getReadableDatabase().rawQuery("SELECT balance.id  "
                        + "FROM balance "
                        + "  LEFT JOIN complex ON complex.id_balance = balance.id "
                        + "WHERE (balance.id_parent = "+parentID+" OR COALESCE(complex.id_ref,-1) = "+parentID+") "
                        + "AND mode = "+MODE_NORMAL, null);

                /*
                cr = db.getReadableDatabase().rawQuery("SELECT balance.id  "
                        + "FROM balance "
                        + "  LEFT JOIN complex ON complex.id_balance = balance.id "
                        + "WHERE (balance.id_parent = ? OR COALESCE(complex.id_ref,-1) = ?) "
                        + "AND mode = ?;",
                        new String[]{"" + parentID, "" + parentID, "" + MODE_NORMAL});
                        */


                /*
                cr = db.getReadableDatabase().rawQuery("SELECT id  "
                        + "FROM complex  "
                        + "WHERE COALESCE(complex.id_ref,-1) = ? ",
                        new String[]{"" + parentID});
                        */

            }

            if (cr.moveToFirst()) {
                result = true;
            }

        }
        return result;
    }

    public double amountApproved(Calendar[] period) {
        double result = 0.0;

        Cursor cr;
        if (mode == MODE_PROGNOSIS) {
            if (period != null) {
                Calendar start = period[0];
                Calendar stop = period[1];
                cr = db.getReadableDatabase().rawQuery("SELECT SUM(amount) "
                        + "FROM balance "
                        + "WHERE id_parent = ? "
                        + "AND date >= ? "
                        + "AND date <= ? "
                        + "AND mode = ?",
                        new String[]{"" + parentID,
                                "" + start.getTimeInMillis(),
                                "" + stop.getTimeInMillis(),
                                "" + MODE_NORMAL});
            } else {
                cr = db.getReadableDatabase().rawQuery("SELECT SUM(amount) "
                        + "FROM balance "
                        + "WHERE id_parent = ? "
                        + "AND mode = ?",
                        new String[]{"" + parentID, "" + MODE_NORMAL});
            }
            if (cr.moveToFirst()) {
                result = cr.getLong(0);
            }
        }
        return result;
    }


    public double getInBalance() {
        if (accountTo != null) {
            Calendar d = Calendar.getInstance(TimeZone.getDefault());
            d.setTimeInMillis(date.getTime() /*accountTo.date*/);

            Calendar r = Calendar.getInstance(TimeZone.getDefault());
            r.setTimeInMillis(accountTo.date.getTime());

            double from = accountTo.paymentsFrom(null);
            double to = accountTo.paymentsTo(new Calendar[]{r, d});
            double debt = accountTo.amount + from - to;

            return debt;
        } else {
            return 0.0;
        }

    }

    public List<Balance> getGraph(Calendar[] dates) {
        List<Balance> result = new ArrayList<Balance>(0);
        if (mode == MODE_PROGNOSIS) {

            if (period <= 0) {
                period = 1;
            }

            Calendar cDate = Calendar.getInstance(TimeZone.getDefault());
            cDate.setTimeInMillis(date.getTime());

            Calendar keepSkip = Calendar.getInstance(TimeZone.getDefault());
            keepSkip.add(Calendar.MONTH, 1);
            keepSkip.set(Calendar.DAY_OF_MONTH, 1);

            // кредит
            if ((flags & 0xFF) == FLAG_PERIOD_CREDIT
                    && accountTo != null
                    && accountTo.type == Account.TYPE_DEBT) {

                Calendar d = Calendar.getInstance(TimeZone.getDefault());
                d.setTimeInMillis(date.getTime() /*accountTo.date*/);

                /*
                Calendar r = Calendar.getInstance(TimeZone.getDefault());
                r.setTimeInMillis(accountTo.date.getTime());

                double debt = accountTo.amount
                        + accountTo.paymentsFrom(null)
                        - accountTo.paymentsTo(new Calendar[]{r, d});
                        */

                double debt = getInBalance();

                int n = 0;
                int a = (int) (debt / amount) + ((debt / amount) - ((int) (debt / amount)) != 0 ? 1 : 0);

                double payed = 0.0;
                if (dates != null) {
                    payed = accountTo.paymentsTo(new Calendar[]{d, dates[1]});
                } else {
                    payed = accountTo.paymentsTo(new Calendar[]{d, Calendar.getInstance(TimeZone.getDefault())});
                    dates = new Calendar[]{d, null};
                }
                double payments = 0.0;

                while (debt > 0) {
                    d.add(Calendar.MONTH, 1);
                    if (dates[1] == null || d.before(dates[1]) || d.equals(dates[1])) {
                        Balance c = copy();
                        //c.parentID = balanceID;
                        //c.mode = MODE_PROGNOSIS;
                        //c.flags |= FLAG_PERIOD_ONCE;
                        c.date = new Date(d.getTimeInMillis());
                        c.name = String.format("%d/%d: %s", ++n, a, c.name);
                        if (amount <= debt) {
                            c.amount = amount;
                        } else {
                            c.amount = debt;
                        }
                        payments += c.amount;

                        // выриант с помесячным контролем
                        //c.inserted = accountTo.paymentsTo(Main.getPeriod(Calendar.MONTH, d)) >= amount;

                        // контроль всей погашеной суммы
                        c.approved = (payed >= payments);

                        if ((!c.approved && !cDate.after(dates[0]) && dates[0].before(keepSkip))
                                || (!d.before(dates[0]) && !d.after(dates[1]))) {

                            //if ((!c.approved && dates[0].getTimeInMillis() < keepSkip.getTimeInMillis())
                            //    || d.getTimeInMillis() >= dates[0].getTimeInMillis()) {
                            result.add(c);
                        }
                        debt -= amount;
                    } else {
                        break;
                    }
                }
            }

            // все остальное только для периода
            if (dates == null) {
                dates = new Calendar[]{
                        Calendar.getInstance(TimeZone.getDefault()),
                        Calendar.getInstance(TimeZone.getDefault())
                };
                dates[0].setTimeInMillis(0L);
            }

            // карта
            if ((flags & 0xFF) == FLAG_PERIOD_CREDIT
                    && accountTo != null
                    && accountTo.type == Account.TYPE_CARD) {
                Calendar d = Calendar.getInstance(TimeZone.getDefault());
                d.setTimeInMillis(date.getTime() /*accountTo.date.getTime()*/);
                while (!d.after(dates[1])) {
                    Balance c = copy();
                    c.date = new Date(d.getTimeInMillis());
                    c.amount = amount;
                    Calendar before = (Calendar) d.clone();
                    before.add(Calendar.MONTH, -1);
                    before.add(Calendar.DAY_OF_MONTH, 1);
                    double payed = accountTo.paymentsTo(new Calendar[]{before, d});
                    c.approved = payed >= amount; //&& accountTo.balanceAt(d)[0] >= amount;
                    //c.inserted = c.approved;
                    //if ((!c.approved && dates[0].before(keepSkip)) || !d.before(dates[0])) {
                    if ((!c.approved && !cDate.after(dates[0]) && dates[0].before(keepSkip))
                            || (!d.before(dates[0]) && !d.after(dates[1]))) {

                        if(c.approved && d.after(Calendar.getInstance()) && accountTo.balanceAt(d)[0] < amount) {
                            c.approved = false;
                        }
                        result.add(c);
                    }
                    d.add(Calendar.MONTH, period);
                }
            }


            //
            if ((flags & 0xFF) == FLAG_PERIOD_ONCE) {
                Balance c = copy();
                c.approved = c.isApproved(null);
                Calendar d = Calendar.getInstance(TimeZone.getDefault());
                d.setTimeInMillis(date.getTime() /*accountTo.date*/);

                if (c.approved) {
                    c.amount = c.amountApproved(null);
                }
                if ((!c.approved && !cDate.after(dates[0]) && dates[0].before(keepSkip))
                        || (!d.before(dates[0]) && !d.after(dates[1]))) {
                    result.add(c);
                }
            }

            if ((flags & 0xFF) == FLAG_PERIOD_DAILY) {
                Calendar d = Calendar.getInstance(TimeZone.getDefault());
                d.setTime(date);
                while (d.getTimeInMillis() <= dates[1].getTimeInMillis()) {
                    Balance c = copy();
                    //c.parentID = balanceID;
                    //c.mode = MODE_PROGNOSIS;
                    //c.flags |= FLAG_PERIOD_DAILY;
                    c.date = new Date(d.getTimeInMillis());
                    c.amount = amount;
                    c.approved = c.isApproved(Main.getPeriod(Calendar.DAY_OF_MONTH, d));
                    if (c.approved) {
                        c.amount = c.amountApproved(null);
                    }
                    //c.inserted = c.approved;
                    //if ((!c.approved && dates[0].getTimeInMillis() < keepSkip.getTimeInMillis())
                    //        || d.getTimeInMillis() >= dates[0].getTimeInMillis()) {
                    if ((!c.approved && !cDate.after(dates[0]) && dates[0].before(keepSkip))
                            || (!d.before(dates[0]) && !d.after(dates[1]))) {

                        result.add(c);
                    }
                    d.add(Calendar.DAY_OF_MONTH, period);
                }
            }

            if ((flags & 0xFF) == FLAG_PERIOD_WEEKLY) {
                Calendar d = Calendar.getInstance(TimeZone.getDefault());
                d.setTime(date);
                while (d.getTimeInMillis() <= dates[1].getTimeInMillis()) {
                    Balance c = copy();
                    //c.parentID = balanceID;
                    //c.mode = MODE_PROGNOSIS;
                    //c.flags |= FLAG_PERIOD_WEEKLY;
                    c.date = new Date(d.getTimeInMillis());
                    c.amount = amount;
                    c.approved = c.isApproved(Main.getPeriod(Calendar.WEEK_OF_MONTH, d));
                    if (c.approved) {
                        c.amount = c.amountApproved(null);
                    }
                    //c.inserted = c.approved;
                    //if ((!c.approved && dates[0].getTimeInMillis() < keepSkip.getTimeInMillis())
                    //        || d.getTimeInMillis() >= dates[0].getTimeInMillis()) {
                    if ((!c.approved && !cDate.after(dates[0]) && dates[0].before(keepSkip))
                            || (!d.before(dates[0]) && !d.after(dates[1]))) {
                        result.add(c);
                    }
                    d.add(Calendar.DAY_OF_MONTH, period * 7);
                }
            }

            if ((flags & 0xFF) == FLAG_PERIOD_MONTHLY) {
                Calendar d = Calendar.getInstance(TimeZone.getDefault());
                d.setTime(date);
                while (d.getTimeInMillis() <= dates[1].getTimeInMillis()) {
                    Balance c = copy();
                    c.parentID = balanceID;
                    c.mode = MODE_PROGNOSIS;
                    c.flags |= FLAG_PERIOD_MONTHLY;
                    c.date = new Date(d.getTimeInMillis());
                    c.amount = amount;
                    c.approved = c.isApproved(Main.getPeriod(Calendar.MONTH, d));
                    if (c.approved) {
                        c.amount = c.amountApproved(null);
                    }
                    //c.inserted = c.approved;
                    //if ((!c.approved && dates[0].getTimeInMillis() < keepSkip.getTimeInMillis())
                    //        || d.getTimeInMillis() >= dates[0].getTimeInMillis()) {
                    if ((!c.approved && !cDate.after(dates[0]) && dates[0].before(keepSkip))
                            || (!d.before(dates[0]) && !d.after(dates[1]))) {
                        result.add(c);
                    }
                    d.add(Calendar.MONTH, period);
                }
            }

            if ((flags & 0xFF) == FLAG_PERIOD_ANNUAL) {
                Calendar d = Calendar.getInstance(TimeZone.getDefault());
                d.setTime(date);
                while (d.getTimeInMillis() <= dates[1].getTimeInMillis()) {
                    Balance c = copy();
                    //c.parentID = balanceID;
                    //c.mode = MODE_PROGNOSIS;
                    //c.flags |= FLAG_PERIOD_MONTHLY;
                    c.date = new Date(d.getTimeInMillis());
                    c.amount = amount;
                    c.approved = c.isApproved(Main.getPeriod(Calendar.YEAR, d));
                    if (c.approved) {
                        c.amount = c.amountApproved(null);
                    }
                    //c.inserted = c.approved;
                    //if ((!c.approved && dates[0].getTimeInMillis() < keepSkip.getTimeInMillis())
                    //        || d.getTimeInMillis() >= dates[0].getTimeInMillis()) {
                    if ((!c.approved && !cDate.after(dates[0]) && dates[0].before(keepSkip))
                            || (!d.before(dates[0]) && !d.after(dates[1]))) {
                        result.add(c);
                    }
                    d.add(Calendar.MONTH, period);
                }
            }

        }
        return result;
    }

    public Balance copy() {
        Balance c = new Balance(db);

        c.balanceID = -1;
        c.parentID = balanceID;
        c.name = name;
        c.type = type;
        c.date = date;
        c.accountFrom = accountFrom;
        c.accountTo = accountTo;
        c.amount = amount;
        c.mode = mode;
        c.flags = flags;
        c.period = period;
        c.number = number;
        c.template = template;
        c.key = key;
        c.remark = remark;
        c.analytics = analytics;
        c.complex = complex;
        c.approved = approved;
        //c.inserted = inserted;

        return c;
    }

    @Override
    public int compareTo(Object o) {
        return date.compareTo(((Balance) o).date);  //To change body of implemented methods use File | Settings | File Templates.
    }

    public double recalcComplex() {
        double ret = amount;

        if (complex.size() > 0) {
            type = TYPE_COMPLEX;
            accountTo = null;

            //ret = 0.0;
            double amount = 0.0;
            boolean was = false;
            do {
                int i = 0;
                was = false;
                for (Complex c : complex) {
                    if (c.type == Complex.TYPE_AUTO) {
                        complex.remove(i);
                        was = true;
                        break;
                    } else {
                        ++i;
                    }
                }
            } while (was);

            for (Complex c : complex) {
                if (c.type == Complex.TYPE_NORMAL) {
                    amount += c.amount;
                }
            }

            if (amount < this.amount) {
                Complex c = new Complex(Main.db, this);
                c.amount = this.amount - amount;
                c.type = Complex.TYPE_AUTO;
                this.complex.add(0, c);
            }

            ret = this.amount - amount;

            /*else if (amount > this.amount) {
                this.amount = amount;
            }
            */

        } else {
            type = TYPE_NORMAL;
        }

        return ret;
    }

    @Override
    public String toString() {
        switch (mode) {
            case MODE_NORMAL:
                return name;
            case MODE_PROGNOSIS:
                return "● " + name;
        }
        //"●■□♦"
        return name;
    }

}
